﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Text;

using System.IO;
using System.Net.Mail;

using GoodStuff;

namespace GoodStuff.SvnBackup
{
    /// <summary>
    /// Subversion-backup program
    /// </summary>
    class Program
    {
        private const string __appSetting_AppPath = "SVNAppPath";
        private const string __appSetting_DataPath = "SVNDataPath";
        private const string __appSetting_BackupPath = "SVNBackupPath";
        private const string __appSetting_BackupMethod = "SVNBackupMethod";
        private const string __appSetting_compress7ZipPath = "7ZipPath";
        private const string __appSetting_compress7ZipOutputFolder = "7ZipOutputFolder";
        private const string __filename_svnadmin_executable = "svnadmin.exe";
        private const string __filename_batchfile_dump = "svnbackup.bat";

        public enum BackupMethod
        {
            /// <summary>
            /// SVN 'dump' method, which pipes all the content to a .bakfile
            /// </summary>
            Dump,
            /// <summary>
            /// SVN 'hotcopy' method, which will copy all the files of the repository 'on the fly'
            /// </summary>
            Hotcopy
        }

        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("Starting Visal SVN Server backupprocedure @ " + startTime.ToLongTimeString());

            //Get application path
            string appPath = ConfigurationHelper.GetSetting<string>(__appSetting_AppPath);
            Console.WriteLine("Applicationpath @ " + appPath);
            if (Directory.Exists(appPath) == false)
                throw new DirectoryNotFoundException(string.Format("Applicationpath '{0}' does not exist. Please verify the appsetting '{1}' within the configurationfile.", appPath, __appSetting_AppPath));

            //Get full path to svnadmin
            string svnadmin = Path.Combine(appPath, __filename_svnadmin_executable);
            Console.WriteLine("Svnadmin location @ " + svnadmin);
            if(File.Exists(svnadmin) == false)
                throw new FileNotFoundException(string.Format("svnadmin-executable not found at '{0}'. Please verify the appsetting '{1}' within the configurationfile.", svnadmin, __filename_svnadmin_executable));

            //Get path of repositories
            string dataPath = ConfigurationHelper.GetSetting<string>(__appSetting_DataPath);
            Console.WriteLine("Datapath @ " + dataPath);
            if(Directory.Exists(dataPath) == false)
                throw new DirectoryNotFoundException(string.Format("Path of repositories '{0}' does not exist. Please verify the appsetting '{1}' within the configurationfile.", appPath, __appSetting_DataPath));

            //Get destination/output path
            string backupPath = ConfigurationHelper.GetSetting<string>(__appSetting_BackupPath);
            if (backupPath.EndsWith("\\") == false)
                backupPath = backupPath + "\\";
            if (Directory.Exists(backupPath) == false)
                throw new DirectoryNotFoundException(string.Format("Outputdirectory '{0}' does not exist. Please verify the appsetting '{1}' within the configurationfile.", backupPath, __appSetting_BackupPath));
            Console.WriteLine("Outputdirectory @ " + backupPath);

            //Get BackupMethod. This is optional, the default is "Dump"
            BackupMethod method = BackupMethod.Dump;
            string methodString;
            if (ConfigurationHelper.TryGetSetting<string>(__appSetting_BackupMethod, out methodString))
            {
                try
                {
                    method = Enum<BackupMethod>.Parse(methodString, true);
                }
                catch (Exception ex)
                {
                    throw new ConfigurationErrorsException(string.Format("The SVN-backupmethod configured with appSetting '{0' in the configurationfile, is not a valid backupmethod. Please use 'Dump' or 'Hotcopy'. InnerExceptionmessage: {2}", __appSetting_BackupMethod, ex.Message));
                }
            }

            //Get optional path to 7Zip file. If not configured, don't compress .bak afterwards
            string compress7ZipPath;
            bool compress = false;
            if (ConfigurationHelper.TryGetSetting<string>(__appSetting_compress7ZipPath, out compress7ZipPath))
            {
                Console.WriteLine("Compress enabled with 7Zip using " + compress7ZipPath);
                Console.WriteLine("After compression, .bak will be removed from outputdirectory");
                compress = true;
            }

            //Optionally the compressed folder can be created in a seperate folder
            string compressOutputFolder = ConfigurationHelper.GetSetting<string>(__appSetting_compress7ZipOutputFolder, backupPath);
           
            if (compressOutputFolder.EndsWith("\\") == false)
                compressOutputFolder = compressOutputFolder + "\\";
            
            // now for each repository, create a backup command 
            // Get an array of all repositories (= all subfolders)
            string[] repositories = Directory.GetDirectories(dataPath);

            Console.WriteLine(repositories.Length + " repositories found to backup.");
            WriteDashes();

            // Save a list of commands to execute
            List<string> commands = new List<string>();

            List<string> zipCommands = new List<string>();

            Console.WriteLine("Creating backup-batchfile");
            foreach (string repositoryDir in repositories)
            {
                string reposName = Path.GetFileName(repositoryDir);
                string backupCommand = null;
                switch (method)
                {
                    case BackupMethod.Dump:
                        backupCommand = string.Format("\"{0}\" dump \"{1}\" > \"{2}{3}.bak\"", svnadmin, repositoryDir, backupPath, reposName);
                        break;
                    case BackupMethod.Hotcopy:
                        backupCommand = string.Format("\"{0}\" hotcopy \"{1}\" \"{2}{3}\"", svnadmin, repositoryDir, backupPath, reposName);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException("backupMethod", method, string.Format("invalid backupMethod '{0}'", method));
                }

                commands.Add(backupCommand);

                if (compress)
                {
                    commands.Add(string.Format("\"{0}\" a \"{1}{2}.zip\" \"{3}{2}.bak\"", compress7ZipPath, compressOutputFolder, reposName, backupPath));
                    commands.Add(string.Format("del \"{0}{1}.bak\"", backupPath, reposName));
                }

                Console.WriteLine(string.Format("   - repository '{0}' added to backupscript", reposName));
            }

            //Create a batch file based on all collected commands. Split the command with a 'newline'.
            File.WriteAllText(__filename_batchfile_dump, string.Join(Environment.NewLine, commands.ToArray()), Encoding.Default);

            //Finally, run the created batchfile.
            WriteDashes();
            Console.WriteLine("Starting backup");
            Execute(__filename_batchfile_dump);
            if (method == BackupMethod.Hotcopy)
            {
                WriteDashes();
                Console.WriteLine("Note: When using hotcopy backupmethod, ensure the outputdirectory is empty.");
                Console.WriteLine("hotcopy can't override existing directories.");
            }
            DateTime finished = DateTime.Now;
            WriteDashes();
            Console.WriteLine("Generating backupscript finished @ " + finished.ToLongTimeString());
            Console.WriteLine("Executing time " + (finished.Subtract(startTime)).ToString());

            // Example
            // "C:\Program Files\VisualSVN Server\bin\svnadmin" dump "G:\Subversion\Repositories\F52_SourcesServer" > "G:\Subversion\Backup\F52_SourcesServer.bak"
        }

        private static void WriteDashes()
        {
            Console.WriteLine("--------------------------------------------------");
        }

        public static void Execute(string filename)
        {
            // Calculate Windows path
            //string cmdExe = System.Environment.GetEnvironmentVariable("ComSpec") + " /c";

            // Launch explorer.exe utility to configure mappings
            System.Diagnostics.Process backup = new System.Diagnostics.Process();
            backup.StartInfo.FileName = filename;
            backup.StartInfo.CreateNoWindow = false;
            backup.StartInfo.UseShellExecute = false;
            backup.Start();
            backup.WaitForExit();
        }
    }
}
